/**
 * Copyright (c) 2011-2019, James Zhan 詹波 (jfinal@126.com).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package yun.web.hotswap;

import yun.web.hotswap.hotswap.HotSwapWatcher;

import java.text.DecimalFormat;

/**
 * Hotswap
 * <p>
 * 官方示例：https://github.com/undertow-io/undertow/tree/master/examples/src/main/java/io/undertow/examples
 * <p>
 * <p>
 * 注意：关闭服务时使用：kill pid，不要使用：kill -9 pid，
 * 否则 JFinalConfig.beforeJFinalStop() 不会被回调
 * <p>
 * 文档：
 * http://undertow.io/undertow-docs/undertow-docs-2.0.0/index.html
 * https://blog.csdn.net/zhaowen25/article/details/45324805
 */
public class Hotswap {

    protected HotswapConfig config;

    protected volatile boolean started = false;
    protected volatile HotSwapWatcher hotSwapWatcher;
    protected DecimalFormat decimalFormat = new DecimalFormat("#.#");

    /**
     * 创建 Hotswap
     * <p>
     * 尝试使用 "undertow.txt" 以及 "undertow-pro.txt" 初始化 undertow
     * 当配置文件不存在时不抛出异常而是使用默认值进行初始化
     */

    public static Hotswap create(String hookClass) {
        return new Hotswap(new HotswapConfig(hookClass));
    }


    protected Hotswap(HotswapConfig hotswapConfig) {
        this.config = hotswapConfig;
        boolean devMode = null == System.getProperty("devMode");

        // 开发者模式启用彩色日志
        if (devMode) System.setProperty("log4j.skipJansi", "false");
    }

    public synchronized void start() {

        try {
            // System.out.println("Starting Undertow Server on port: " + config.getPort());
            String msg = " --- hotswap start ---//";
            System.out.println(msg);
            long start = System.currentTimeMillis();
            doStart();
            System.out.println("Starting Complete in " + getTimeSpent(start) + " seconds.\n");

            /**
             * 使用 kill pid 命令或者 ctrl + c 关闭 JVM 时，调用 Hotswap.stop() 方法，
             * 以便触发 JFinalConfig.beforeJFinalStop();
             *
             * 注意：下方代码严格测试过，只支持 kill pid 不支持 kill -9 pid
             */
            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    Hotswap.this.stop();
                }
            });

        } catch (Exception e) {
            e.printStackTrace();
            stopSilently();

            // 支持在 doStart() 中抛出异常后退出 JVM，例如端口被占用，否则在 linux 控制台 JVM 并不会退出
            System.exit(1);
        }
    }

    protected boolean notBlank(String str) {
        return str != null && !"".equals(str.trim());
    }

    protected void doStart() {
        if (started) {
            return;
        }

        hook();

        if (isDevMode() && hotSwapWatcher == null) {
            hotSwapWatcher = new HotSwapWatcher(this);
            hotSwapWatcher.start();
        }

        started = true;
    }


    public synchronized void stop() {
        if (started) {
            started = false;
        } else {
            return;
        }

        System.out.println("\nShutdown Undertow Server ......");
        long start = System.currentTimeMillis();
        try {
            if (hotSwapWatcher != null) {
                hotSwapWatcher.exit();
            }

            doStop();

        } catch (Exception e) {
            e.printStackTrace();
            stopSilently();
        } finally {
            System.out.println("Shutdown Complete in " + getTimeSpent(start) + " seconds. See you later (^_^)\n");
        }
    }

    protected void doStop() {
        // 保留以下三行
        // deploymentManager.undeploy();
        // Servlets.defaultContainer().removeDeployment(deploymentInfo);
        // undertow.stop();

        /**
         * 必须设置 HotSwapWatcher.setDaemon(false)，否则下面两行代码将退出 JVM，无法再次启动 undertow
         * 触发 JFinalConfig.beforeJFinalStop() 方法必须要调用 deploymentManager.stop()
         * 该方法不能在 deploymentManager.undeploy() 这后调用，否则有 NPE
         */
        //deploymentManager.stop();
        //undertow.stop();
        hookItf.stop();
    }

    /**
     * HotSwapWatcher 调用 restart()
     */
    public synchronized void restart() {
        if (started) {
            started = false;
        } else {
            return;
        }

        try {
            System.err.println("\nLoading changes ......");
            long start = System.currentTimeMillis();

            doStop();
            config.replaceClassLoader();
            Thread.sleep(1000);
            config.replaceClassLoader();
            doStart();

            System.err.println("Loading complete in " + getTimeSpent(start) + " seconds (^_^)\n");

        } catch (Exception e) {
            System.err.println("Error restarting webapp after change in watched files");
            e.printStackTrace();
        }
    }

    protected String getTimeSpent(long startTime) {
        float timeSpent = (System.currentTimeMillis() - startTime) / 1000F;
        return decimalFormat.format(timeSpent);
    }

    HookItf hookItf;

    protected void stopSilently() {
        try {
            started = false;
            if (hookItf != null)
                hookItf.stop();
        } catch (Exception e) {
            HotswapKit.doNothing(e);
        }
    }


    protected void hook() {

        try {
            //Thread.sleep(1000);
            hookItf = (HookItf) Class.forName(this.config.hookClass, false, HookItf.class.getClassLoader()).newInstance();
            hookItf.start();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    public boolean isDevMode() {
        return HotswapConfig.isDevMode();
    }

    public boolean isStarted() {
        return started;
    }

    // ------------------

    /**
     * 仅用于解决项目的 JFinalConfig 继承类打成 jar 包，并且使用 undertow.devMode=true 配置
     * 时报出的异常，以上两个条件没有同时成立时无需理会，也就是说没有报异常就无需理会
     * <p>
     * 假定项目中的 JFinalConfig 的继承类 com.abc.MyConfig 被打进了 jar 包并且
     * undertow.devMode 设置成了 true，这里在启动项目的时候由于 ClassLoader
     * 不同会报出以下异常：
     * Can not create instance of class: com.abc.MyConfig. Please check the config in web.xml
     * <p>
     * 解决办法是使用 addHotSwapClassPrefix(...) :
     * Hotswap.create(MyConfig.class).addHotSwapClassPrefix("com.abc.").start();
     * <p>
     * 只添加 JFinalConfig 的继承类 com.abc.MyConfig 也可以：
     * Hotswap.create(MyConfig.class).addHotSwapClassPrefix("com.abc.MyConfig").start();
     * <p>
     * 注意：该配置对生产环境无任何影响，在打包部署前无需删除该配置
     */
    public Hotswap addHotSwapClassPrefix(String prefix) {
        config.addHotSwapClassPrefix(prefix);
        return this;
    }

    public Hotswap addSystemClassPrefix(String prefix) {
        config.addSystemClassPrefix(prefix);
        return this;
    }
}






